/**
 * Copyright (C) 2017 - 2020 https://github.com/joewee
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.bifrost.service.impl;

import com.alibaba.fastjson.JSON;
import com.bifrost.common.constant.SessionConstant;
import com.bifrost.constant.OrderType;
import com.bifrost.constant.RunningMode;
import com.bifrost.entity.MerchantStore;
import com.bifrost.entity.Order;
import com.bifrost.entity.OrderTransaction;
import com.bifrost.event.OrderPaidEvent;
import com.bifrost.repository.OrderAttributeRepository;
import com.bifrost.repository.OrderTransactionRepository;
import com.bifrost.service.OrderService;
import com.bifrost.service.PayService;
import com.bifrost.uid.UidGenerator;
import com.bifrost.util.*;
import com.bifrost.wechat.entity.WechatAccount;
import com.bifrost.wechat.service.WechatAccountService;
import org.apache.commons.lang.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigInteger;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;

/**
 *基于威富通的支付服务
 *
 *@author joewee
 *@version 1.0.0
 *@Date 2018/3/13 13:07
 */
@Service
public class SwiftPassGateWayService implements PayService,ApplicationContextAware {

    private static Logger LOGGER = LoggerFactory.getLogger(PayService.class);
    private final static String VERSION = "2.0";
    private final static String CHARSET = "UTF-8";
    private final static String SIGN_TYPE = "MD5";

    @Value("${swiftpass.notify.url:#{null}}")
    private String swiftpass_notify_url;
    @Value("${swiftpass.pay.api:#{null}}")
    private String swiftpass_pay_api;
    @Value("${swiftpass.call.back.url:#{null}}")
    private String call_back_url;
    @Value("${running.mode:#{null}}")
    private String runningMode;
    @Autowired
    private OrderService orderService;
    @Autowired
    private WechatAccountService wechatAccountService;
    @Autowired
    OrderTransactionRepository orderTransactionRepository;
    protected ApplicationContext applicationContext;
    @Autowired
    UidGenerator uidGenerator;
    @Autowired
    OrderAttributeRepository orderAttributeRepository;
    /**
     * 获取预支付信息
     *
     * @param req
     * @param resp
     * @return
     * @throws ServletException
     * @throws IOException
     */
    @Override
    public Map<String, String> prePay(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        if(LOGGER.isDebugEnabled()){
            LOGGER.debug("开始执行支付请求...");
        }
        SortedMap<String, String> map = XmlUtils.getParameterMap(req);
        Order order = orderService.findOrderByOutTradeNo(map.get("out_trade_no"));
        if (order == null) {
            throw new RuntimeException("订单异常");
        }
        WechatAccount account = getWechatAccount(map);
       if(account!=null){
           map.put("service", "pay.weixin.jspay");
           map.put("version", VERSION);
           map.put("charset", CHARSET);
           map.put("sign_type", SIGN_TYPE);
           map.put("is_raw", "1");
           map.put("mch_id", account.getMchId().toString());
           map.put("notify_url", swiftpass_notify_url);
           map.put("callback_url",String.format("%s=%s",call_back_url,map.get("total_fee")));
           map.put("nonce_str", String.valueOf(System.currentTimeMillis()));
           map.put("sub_openid",req.getAttribute(SessionConstant.OPEN_ID).toString());
           map.put("sub_appid",account.getSubAppId()==null?account.getAppId():account.getSubAppId());
           map.put("mch_create_ip", IPUtils.getIpAddr(req));
           Date startDate =order.getDatePurchased();
           LocalDateTime expired  =LocalDateUtils.dateTimeFromDate(startDate).plus(30, ChronoUnit.MINUTES);
           map.put("time_start", DateUtils.format(startDate,"yyyyMMddHHmmss"));
           map.put("time_expire",DateUtils.format(expired,"yyyyMMddHHmmss"));
           Map<String, String> params = SignUtils.paraFilter(map);
           StringBuilder buf = new StringBuilder((params.size() + 1) * 10);
           SignUtils.buildPayParams(buf, params, false);
           String preStr = buf.toString();
           String sign = MD5.sign(preStr, "&key=" +account.getMchKey(), "utf-8");
           map.put("sign", sign);
           if(LOGGER.isDebugEnabled()){
               LOGGER.debug("支付接口地址：" + swiftpass_pay_api);
               LOGGER.debug("请求参数:" + XmlUtils.parseXML(map));
           }
           CloseableHttpResponse response;
           response = null;
           CloseableHttpClient client = null;
           String res = null;
           Map<String, String> resultMap = null;
           try {
               HttpPost httpPost = new HttpPost(swiftpass_pay_api);
               StringEntity entityParams = new StringEntity(XmlUtils.parseXML(map), "utf-8");
               httpPost.setEntity(entityParams);
               httpPost.setHeader("Content-Type", "text/xml;utf-8");
               client = HttpClients.createDefault();
               response = client.execute(httpPost);
               if (response != null && response.getEntity() != null) {
                   resultMap = XmlUtils.toMap(EntityUtils.toByteArray(response.getEntity()), "utf-8");
                   res = XmlUtils.toXml(resultMap);
                   if(LOGGER.isDebugEnabled()){
                       LOGGER.debug("返回结果：" + res);
                   }
                   if (!SignUtils.checkParam(resultMap, account.getMchKey())) {
                       throw new RuntimeException("验证签名不通过");
                   } else {
                       if ("0".equals(resultMap.get("status")) && "0".equals(resultMap.get("result_code"))) {
                           String pay_info = resultMap.get("pay_info");
                           if(LOGGER.isDebugEnabled()){
                               LOGGER.debug("预支付信息 : " + pay_info);
                           }
                           //记录订单交易事件日志
                           OrderTransaction transaction = new OrderTransaction();
                           transaction.setOrder(order);
                           transaction.setPaymentType(order.getPayment().getDescription());
                           transaction.setTransactionAmount(map.get("total_fee").toString());
                           transaction.setTransactionDate(new Date());
                           transaction.setTransactionType("pay.weixin.jspay");
                           transaction.setUpdatedTimestamp(new Date());
                           transaction.setTransactionDetails(JSON.toJSONString(resultMap));
                           transaction.setBusinessId(resultMap.get("token_id"));
                           orderTransactionRepository.save(transaction);
                           return resultMap;
                       }
                   }
               } else {
                   throw new RuntimeException("无法获取结果请检查网络");
               }
           } catch (Exception e) {
               LOGGER.error("操作失败，原因：",e);
               throw new RuntimeException(e);
           } finally {
               if (response != null) {
                   response.close();
               }
               if (client != null) {
                   client.close();
               }
           }
           return resultMap;
        }
        throw  new RuntimeException(String.format("门店支付配置不正确"));
    }

    /**
     * 订单查询
     *
     * @param outTradeNo
     * @param transactionId
     * @return
     * @throws ServletException
     * @throws IOException
     */
    @Override
    public Map<String, String> query(String outTradeNo, String transactionId) throws ServletException, IOException {
        if(LOGGER.isDebugEnabled()){
            LOGGER.debug("开始查询订单...");
        }
        if(org.apache.commons.lang3.StringUtils.isAllBlank(outTradeNo,transactionId)){
            throw new RuntimeException("outTradeNo和transactionId不能同时为空");
        }

        SortedMap<String, String> map = new TreeMap();
        map.put("out_trade_no",outTradeNo);
        map.put("transaction_id",transactionId);
        WechatAccount account = getWechatAccount(map);
        if(account==null){throw  new RuntimeException("门店支付配置不正确");}
        map.put("service", "unified.trade.query");
        map.put("version", VERSION);
        map.put("charset", CHARSET);
        map.put("sign_type", SIGN_TYPE);
        map.put("mch_id", account.getMchId().toString());
        map.put("nonce_str", String.valueOf(System.currentTimeMillis()));
        Map<String,String> params = SignUtils.paraFilter(map);
        StringBuilder buf = new StringBuilder((params.size() +1) * 10);
        SignUtils.buildPayParams(buf,params,false);
        String preStr = buf.toString();
        String sign = MD5.sign(preStr, "&key=" + account.getMchKey(), "utf-8");
        map.put("sign", sign);

        LOGGER.debug("reqUrl:" + swiftpass_pay_api);

        CloseableHttpResponse response = null;
        CloseableHttpClient client = null;
        Map<String,String> resultMap = null;
        try {
            HttpPost httpPost = new HttpPost(swiftpass_pay_api);
            StringEntity entityParams = new StringEntity(XmlUtils.parseXML(map),"utf-8");
            httpPost.setEntity(entityParams);
            httpPost.setHeader("Content-Type", "text/xml;utf-8");
            client = HttpClients.createDefault();
            response = client.execute(httpPost);

            if(response != null && response.getEntity() != null){
                resultMap = XmlUtils.toMap(EntityUtils.toByteArray(response.getEntity()), "utf-8");
                LOGGER.debug("请求结果：" + JSON.toJSONString(resultMap));
                if(!SignUtils.checkParam(resultMap, account.getMchKey())){
                    LOGGER.error("验证签名不通过");
                    throw new RuntimeException("验证签名不通过");
                }else{
                    return  resultMap;
                }
            }else{
                throw new RuntimeException("无法获取结果请检查网络");
            }
        } catch (Exception e) {
            LOGGER.error("操作失败，原因：",e);
            throw new RuntimeException(e);
        } finally {
            if(response != null){
                response.close();
            }
            if(client != null){
                client.close();
            }
        }
    }

    /**
     * 退款查询
     *
     * @param req
     * @param resp
     * @return
     * @throws ServletException
     * @throws IOException
     */
    @Override
    public Map<String, String> refundQuery(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        if(LOGGER.isDebugEnabled()){
            LOGGER.debug("开始执行退款查询...");
        }
        SortedMap<String, String> map = XmlUtils.getParameterMap(req);
        WechatAccount account = getWechatAccount(map);
        if(account==null){throw  new RuntimeException("门店支付配置不正确");}

        map.put("service", "unified.trade.refundquery");
        map.put("version", VERSION);
        map.put("charset", CHARSET);
        map.put("sign_type", SIGN_TYPE);

        String key = account.getMchKey();
        String reqUrl = swiftpass_pay_api;
        map.put("mch_id", account.getMchId().toString());
        map.put("nonce_str", String.valueOf(System.currentTimeMillis()));

        Map<String,String> params = SignUtils.paraFilter(map);
        StringBuilder buf = new StringBuilder((params.size() +1) * 10);
        SignUtils.buildPayParams(buf,params,false);
        String preStr = buf.toString();
        String sign = MD5.sign(preStr, "&key=" + key, "utf-8");
        map.put("sign", sign);

        LOGGER.debug("reqUrl:" + reqUrl);

        CloseableHttpResponse response = null;
        CloseableHttpClient client = null;
        String res = null;
        try {
            HttpPost httpPost = new HttpPost(reqUrl);
            StringEntity entityParams = new StringEntity(XmlUtils.parseXML(map),"utf-8");
            httpPost.setEntity(entityParams);
            httpPost.setHeader("Content-Type", "text/xml;utf-8");
            client = HttpClients.createDefault();
            response = client.execute(httpPost);
            if(response != null && response.getEntity() != null){
                Map<String,String> resultMap = XmlUtils.toMap(EntityUtils.toByteArray(response.getEntity()), "utf-8");
                res = XmlUtils.toXml(resultMap);
                LOGGER.debug("请求结果：" + res);

                if(!SignUtils.checkParam(resultMap, key)){
                    res = "验证签名不通过";
                }
            }else{
                res = "操作失败!";
            }
        } catch (Exception e) {
            LOGGER.error("操作失败，原因：",e);
            res = "操作失败";
        } finally {
            if(response != null){
                response.close();
            }
            if(client != null){
                client.close();
            }
        }
        Map<String,String> result = new HashMap<String,String>();
        if(res.startsWith("<")){
            result.put("status", "200");
            result.put("msg", "操作成功，请在日志文件中查看");
        }else{
            result.put("status", "500");
            result.put("msg", res);
        }
        return result;
    }

    /**
     * 退款
     *
     * @param map
     * @return
     * @throws ServletException
     * @throws IOException
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public Map<String,String>  refund(SortedMap<String, String> map) throws  IOException {
        Order order = orderService.findOrderByOutTradeNo(map.get("out_trade_no"));
        if(LOGGER.isDebugEnabled()){
            LOGGER.debug("构建退款参数...");
        }
        //SortedMap<String, String> map = XmlUtils.getParameterMap(req);
        if(!map.containsKey("out_refund_no")){
            map.put("out_refund_no",""+uidGenerator.getUID());
        }
        WechatAccount account = getWechatAccount(map);
        if(account==null){throw  new RuntimeException("门店支付配置不正确");}
        map.put("service", "unified.trade.refund");
        map.put("version", VERSION);
        map.put("charset", CHARSET);
        map.put("sign_type", SIGN_TYPE);

        String key = account.getMchKey();
        String reqUrl = swiftpass_pay_api;
        map.put("mch_id", account.getMchId().toString());
        map.put("op_user_id", account.getMchId().toString());
        map.put("nonce_str", String.valueOf(System.currentTimeMillis()));

        /*if (RunningMode.DEVELOPMENT.equalsIgnoreCase(runningMode)) {
            map.put("total_fee","1");
            map.put("refund_fee","1");
        }*/
        Map<String,String> params = SignUtils.paraFilter(map);
        StringBuilder buf = new StringBuilder((params.size() +1) * 10);
        SignUtils.buildPayParams(buf,params,false);
        String preStr = buf.toString();
        if(LOGGER.isDebugEnabled()){
            LOGGER.debug("参数签名...");
        }
        String sign = MD5.sign(preStr, "&key=" + key, "utf-8");
        map.put("sign", sign);
        if(LOGGER.isDebugEnabled()){
            LOGGER.debug("调用退款接口："+reqUrl);
        }
        CloseableHttpResponse response = null;
        CloseableHttpClient client = null;
        String res = null;
        try {
            HttpPost httpPost = new HttpPost(reqUrl);
            StringEntity entityParams = new StringEntity(XmlUtils.parseXML(map),"utf-8");
            httpPost.setEntity(entityParams);
            httpPost.setHeader("Content-Type", "text/xml;utf-8");
            client = HttpClients.createDefault();
            response = client.execute(httpPost);
            if(response != null && response.getEntity() != null){
                Map<String,String> resultMap = XmlUtils.toMap(EntityUtils.toByteArray(response.getEntity()), "utf-8");
                res = JSON.toJSONString(resultMap);
                if(LOGGER.isDebugEnabled()){
                    LOGGER.debug("请求结果：" +res);
                }
                if(!SignUtils.checkParam(resultMap, key)){
                   throw  new RuntimeException("验证签名不通过");
                }else{
                    OrderTransaction transaction =new OrderTransaction();
                    transaction.setOrder(order);
                    transaction.setPaymentType(order.getPayment().getDescription());
                    transaction.setTransactionAmount(map.get("refund_fee"));
                    transaction.setTransactionDate(new Date());
                    transaction.setTransactionType("unified.trade.refund");
                    transaction.setUpdatedTimestamp(new Date());
                    transaction.setTransactionDetails(res);
                    transaction.setBusinessId(resultMap.get("transaction_id"));
                    orderTransactionRepository.save(transaction);
                  return  resultMap;
                }
            }else{
                throw  new RuntimeException("退款失败:无响应");
            }
        } catch (Exception e) {
            LOGGER.error("退款失败",e);
        } finally {
            if(response != null){
                response.close();
            }
            if(client != null){
                client.close();
            }
        }
        return  null;
    }

    /**
     * 通知
     *TODO 并发锁
     * @param req
     * @param resp
     * @return
     */
    @Override
    public String notify(HttpServletRequest req, HttpServletResponse resp) {
        String respString = "error";
        try {
            req.setCharacterEncoding("utf-8");
            resp.setCharacterEncoding("utf-8");
            resp.setHeader("Content-type", "text/html;charset=UTF-8");
            String resString = XmlUtils.parseRequst(req);
            if(LOGGER.isDebugEnabled()){
                LOGGER.debug("请求的内容：" + resString);
            }
            if(resString != null && !"".equals(resString)){
                Map<String,String> map = XmlUtils.toMap(resString.getBytes(), "utf-8");
                String res = XmlUtils.toXml(map);
               String otn = map.get("out_trade_no");
                Order order = orderService.findOrderByOutTradeNo(otn);
                if(LOGGER.isDebugEnabled()){
                    LOGGER.debug(String.format("收到订单：%d-%s通知...",order.getOrderId(),otn));
                    LOGGER.debug("请求结果：" + res);
                }
                WechatAccount  wechatAccount = getWechatAccount(map);
                if(wechatAccount==null){throw  new RuntimeException("门店支付配置不正确");}
                if(map.containsKey("sign")){
                    if(!SignUtils.checkParam(map, wechatAccount.getMchKey())){
                        res = "验证签名不通过";
                        respString = "error";
                    }else{
                        String status = map.get("status");
                        String result_code = map.get("result_code");
                        if(status != null && "0".equals(status)&&"0".equals(result_code)){
                            //支付成功
                            orderService.handleCouponOrder(order);
                            String totalFee = map.get("total_fee");
                            //记录交易日志
                            OrderTransaction transaction = new OrderTransaction();
                            transaction.setOrder(order);
                            transaction.setPaymentType(order.getPayment().getDescription());
                            transaction.setTransactionAmount(totalFee);
                            transaction.setTransactionDate(new Date());
                            transaction.setTransactionType("notify");
                            transaction.setBusinessId(map.get("transaction_id"));
                            transaction.setUpdatedTimestamp(new Date());
                            transaction.setTransactionDetails(String.format("交易成功：%s", JSON.toJSONString(map)));
                            orderTransactionRepository.save(transaction);
                            /*
                            //是积分优惠券订单不穿小票
                            if(OrderType.BONUS_POINTS_BY_COUPON.equalsIgnoreCase(order.getOrderType())){
                            }else{
                            */
                                OrderPaidEvent orderPaidEvent = new OrderPaidEvent(otn, new BigInteger(totalFee));
                                applicationContext.publishEvent(orderPaidEvent);
                            //}
                            respString = "success";
                        }
                    }
                }
            }
        } catch (Exception e) {
            LOGGER.error("操作失败，原因：",e);
        }
        return respString;
    }

    private WechatAccount getWechatAccount(Map<String, String> map){
      String outTradeNo=null;
      Order order =null;
      if(map.containsKey("out_trade_no")){
          outTradeNo=map.get("out_trade_no");
          order=orderService.findOrderByOutTradeNo(outTradeNo);
      }
      if(order==null){
          throw new RuntimeException("无效的交易号："+outTradeNo);
      }
      MerchantStore mch= order.getMerchantStore();
      WechatAccount account = wechatAccountService.getAccount(mch.getMerchantId());
      if(account==null||account.getMchId()==null|| StringUtils.isEmpty(account.getMchKey())){
          throw  new RuntimeException(String.format("门店%s-%d支付配置不正确",mch.getStoreName(),mch.getMerchantId()));
      }
      else{
          return account;
      }

  }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext=applicationContext;
    }
}
